package bs.org.suite.util;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * Find *.dll, *.jnilib and *.so files, according to the current OS (win, linux, mac).
 * 
 * The library files are automatically extracted from this project's package (JAR).
 * 
 * From  sqlite-jdbc-3.7.2.jar  org.sqlite.OSInfo.java.
 * 
 * http://www.xerial.org/trac/Xerial/wiki/SQLiteJDBC.
 * 
 * Apache License version 2.0.
 * 
 * Modify by Baishui2004 2013-1-16.
 * 
 */
public final class ExecLoader {

	private static boolean extracted = false;

	public static boolean initialize(ExecBean execBean) {
		loadExecNativeLibrary(execBean);
		return extracted;
	}

	private static void loadExecNativeLibrary(ExecBean execBean) {
		if (extracted) {
			return;
		}

		// Load the os-dependent library from a jar file
		String nativeLibFolderPathForCurrentOS = OSInfo.getNativeLibFolderPathForCurrentOS();
		String nativeLibraryFilePath = execBean.getExecFilePathMap().get(nativeLibFolderPathForCurrentOS);

		// use nested VM version
		if (ExecLoader.class.getResource(nativeLibraryFilePath) == null) {
			return;
		}

		// temporary library folder
		String tempFolder = new File(System.getProperty("java.io.tmpdir")).getAbsolutePath();
		// Try extracting the library from jar 
		int lastIndex = nativeLibraryFilePath.lastIndexOf("/");
		if (extractAndLoadLibraryFile(nativeLibraryFilePath.substring(0, lastIndex + 1),
				nativeLibraryFilePath.substring(lastIndex + 1), tempFolder, execBean)) {
			extracted = true;
			return;
		}

		extracted = false;
		return;
	}

	/**
	 * Extract the specified library file to the target folder
	 * 
	 * @param nativeLibraryPath
	 * @param libraryFileName
	 * @param targetFolder
	 * @param execBean
	 * @return
	 */
	private static boolean extractAndLoadLibraryFile(String nativeLibraryPath, String libraryFileName, String targetFolder, ExecBean execBean) {
		String nativeLibraryFilePath = nativeLibraryPath + libraryFileName;
		final String prefix = execBean.getName() + "-" + execBean.getVersion() + "-";

		String extractedLibFileName = (prefix + nativeLibraryPath.replace("/", "-") + libraryFileName).replace("--", "-") ;
		File extractedLibFile = new File(targetFolder, extractedLibFileName);

		try {
			if (extractedLibFile.exists()) {
				// test md5sum value
				String md5sum1 = md5sum(ExecLoader.class.getResourceAsStream(nativeLibraryFilePath));
				String md5sum2 = md5sum(new FileInputStream(extractedLibFile));
				if (md5sum1.equals(md5sum2)) {
					return loadNativeLibrary(targetFolder, extractedLibFileName);
				} else {
					// remove old native library file
					boolean deletionSucceeded = extractedLibFile.delete();
					if (!deletionSucceeded) {
						throw new IOException("failed to remove existing native library file: "
								+ extractedLibFile.getAbsolutePath());
					}
				}
			}

			// extract file into the current directory
			InputStream reader = ExecLoader.class.getResourceAsStream(nativeLibraryFilePath);
			FileOutputStream writer = new FileOutputStream(extractedLibFile);
			byte[] buffer = new byte[1024];
			int bytesRead = 0;
			while ((bytesRead = reader.read(buffer)) != -1) {
				writer.write(buffer, 0, bytesRead);
			}
			writer.close();
			reader.close();

			if (!System.getProperty("os.name").contains(OSInfo.OS_WINDOWS)) {
				try {
					Runtime.getRuntime().exec(new String[] { "chmod", "755", extractedLibFile.getAbsolutePath() })
							.waitFor();
				} catch (Throwable e) {
				}
			}
			return loadNativeLibrary(targetFolder, extractedLibFileName);
		} catch (IOException e) {
			System.err.println(e.getMessage());
			return false;
		}

	}

	private static synchronized boolean loadNativeLibrary(String path, String name) {
		File libPath = new File(path, name);
		if (libPath.exists()) {
			try {
				System.load(new File(path, name).getAbsolutePath());
				return true;
			} catch (UnsatisfiedLinkError e) {
				System.err.println(e);
				return false;
			}

		} else
			return false;
	}

	/**
	 * Computes the MD5 value of the input stream
	 * 
	 * @param input
	 * @return
	 * @throws IOException
	 * @throws NoSuchAlgorithmException
	 */
	static String md5sum(InputStream input) throws IOException {
		BufferedInputStream in = new BufferedInputStream(input);
		try {
			MessageDigest digest = java.security.MessageDigest.getInstance("MD5");
			DigestInputStream digestInputStream = new DigestInputStream(in, digest);
			for (; digestInputStream.read() >= 0;) {
			}
			ByteArrayOutputStream md5out = new ByteArrayOutputStream();
			md5out.write(digest.digest());
			return md5out.toString();
		} catch (NoSuchAlgorithmException e) {
			throw new IllegalStateException("MD5 algorithm is not available: " + e);
		} finally {
			in.close();
		}
	}

}
